/**************************************************************************
*  Black Knight - Chess Engine                                            *
*  Copyright (C) 2009  Victor Rusu                                        *
*                                                                         *
*  This program is free software: you can redistribute it and/or modify   *
*  it under the terms of the GNU General Public License as published by   *
*  the Free Software Foundation, either version 3 of the License, or      *
*  (at your option) any later version.                                    *
*                                                                         *
*  This program is distributed in the hope that it will be useful,        *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the           *
*  GNU General Public License for more details.                           *
*                                                                         *
*  You should have received a copy of the GNU General Public License      *
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
**************************************************************************/

#include <cstring>
#include <string>
#include <sstream>
#include <algorithm>
#include "logic/position.h"
#include <iostream>

const std::string Position::kStartPosition =
    "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
//"R7/8/8/4q2K/8/8/8/8 w - - 0 0";

Position::Position(std::string fen_position) {
    std::memset(board_, false, sizeof(board_));
    std::memset(castle_, false, sizeof(castle_));

    std::istringstream fen(fen_position);
    std::string position, to_move, castle;
    fen >> position >> to_move >> castle >> en_passant_ >>
        moves_since_capture_ >> move_number_;

    int x, y, index;
    for (y = 7, x = index = 0; index < (int)position.size(); ++index)
        switch (position[index]) {
            case ('K') : board_[white][king][x++][y] = true; break;
            case ('Q') : board_[white][queen][x++][y] = true; break;
            case ('R') : board_[white][rook][x++][y] = true; break;
            case ('N') : board_[white][knight][x++][y] = true; break;
            case ('B') : board_[white][bishop][x++][y] = true; break;
            case ('P') : board_[white][pawn][x++][y] = true; break;

            case ('k') : board_[black][king][x++][y] = true; break;
            case ('q') : board_[black][queen][x++][y] = true; break;
            case ('r') : board_[black][rook][x++][y] = true; break;
            case ('n') : board_[black][knight][x++][y] = true; break;
            case ('b') : board_[black][bishop][x++][y] = true; break;
            case ('p') : board_[black][pawn][x++][y] = true; break;

            case ('/') : x = 0, y--; break;

            default : x += position[index] - '0';
        }

    if (to_move == "w")
        side_to_move_ = white;
    else
        side_to_move_ = black;

    if (castle.find("K") != -1)
        castle_[white][king] = true;
    if (castle.find("Q") != -1)
        castle_[white][queen] = true;
    if (castle.find("k") != -1)
        castle_[black][king] = true;
    if (castle.find("q") != -1)
        castle_[black][queen] = true;

    compute_attacked();
}

Position::Position(const Position& copy) {
    std::memcpy(board_, copy.board_, sizeof(board_));
    std::memcpy(castle_, copy.castle_, sizeof(castle_));
    std::memcpy(attacked_, copy.attacked_, sizeof(attacked_));

    side_to_move_ = copy.side_to_move_;

    moves_since_capture_ = copy.moves_since_capture_;
    move_number_ = copy.move_number_;

    en_passant_ = copy.en_passant_;
}

Position::~Position() {
}

Position& Position::operator=(const Position& rvalue) {
    std::memcpy(board_, rvalue.board_, sizeof(board_));
    std::memcpy(castle_, rvalue.castle_, sizeof(castle_));
    std::memcpy(attacked_, rvalue.attacked_, sizeof(attacked_));

    side_to_move_ = rvalue.side_to_move_;

    moves_since_capture_ = rvalue.moves_since_capture_;
    move_number_ = rvalue.move_number_;

    en_passant_ = rvalue.en_passant_;
}

void Position::get_coordinates(char cx, char cy, int& x, int& y) {
    x = cx - 'a';
    y = cy - '1';
}

std::string Position::give_coordinates(int x, int y) {
    std::string board_position;
    board_position += char(x + 'a');
    board_position += char(y + '1');
    return board_position;
}

bool Position::is_on_board(int x, int y) {
    return (0 <= x && x < kBoardSize && 0 <= y && y < kBoardSize);
}

bool Position::is_piece(side _side, int x, int y) {
    if (!is_on_board(x, y))
        return false;
    for (int _piece = 0; _piece < kPieceTypes; ++_piece)
        if (board_[_side][_piece][x][y])
            return true;
    return false;
}

bool Position::can_move_to(int x, int y) {
    if (!is_on_board(x, y))
        return false;
    if (is_piece(side_to_move_, x, y))
        return false;
    else
        return true;
}

bool Position::is_attacked(side _side, int x, int y) {
  return attacked_[_side][x][y];
}

void Position::compute_attacked() {
    //TODO: The method treats all moves as possible capture moves
    //  ex: forward pawn moves should not be considered capture moves

    std::vector<std::string> moves;
    std::memset(attacked_, false, sizeof(attacked_));

    moves = get_all_moves(white);
    for (int index = 0; index < (int)moves.size(); ++index) {
        int x, y;
        get_coordinates(moves[index][2], moves[index][3], x, y);
        attacked_[white][x][y] = true;
    }

    moves = get_all_moves(black);
    for (int index = 0; index < (int)moves.size(); ++index) {
        int x, y;
        get_coordinates(moves[index][2], moves[index][3], x, y);
        attacked_[black][x][y] = true;
    }
}

bool Position::is_checked(side _side) {
    for (int x = 0; x < kBoardSize; ++x)
        for (int y = 0; y < kBoardSize; ++y)
            if (board_[_side][king][x][y])
                return is_attacked((side)!_side, x, y);
    return false;
}

bool Position::is_checked_mate() {
    return (is_checked(to_move()) && !get_valid_moves(to_move()).size());
}

bool Position::is_stale_mate() {
    return (!is_checked(to_move()) && !get_valid_moves(to_move()).size());
}

Position::side Position::to_move() {
    return side_to_move_;
}

bool Position::is_valid_move(std::string move) {
    std::vector<std::string> valid_moves = get_all_moves(side_to_move_);
    return (std::find(valid_moves.begin(), valid_moves.end(), move) !=
     valid_moves.end());
}

std::vector<std::string>
 Position::get_piece_moves(side _side, piece _piece, int x, int y) {
    std::vector<std::string> moves;

    if (_piece == king) {
        // Normal move
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                if (dx || dy) {
                    int new_x = x + dx;
                    int new_y = y + dy;
                    if (!is_on_board(new_x, new_y)) continue;
                    if (!is_piece(_side, new_x, new_y))
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y));
                }

        // Castle move
        int start_line[kSides] = {0, 7};
        if (x == 4 && y == start_line[_side]) {
            // King side
            if (castle_[_side][king] &&
             !is_piece(_side, x + 1, y) &&
             !is_piece((side)!_side, x + 1, y) &&
             !is_attacked((side)!_side, x + 1, y) &&
             !is_piece(_side, x + 2, y) &&
             !is_piece((side)!_side, x + 2, y) &&
             !is_attacked((side)!_side, x + 2, y)) {
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(x + 2, y));
            }

            // Queen side
            if (castle_[_side][queen] &&
             !is_piece(_side, x - 1, y) &&
             !is_piece((side)!_side, x - 1, y) &&
             !is_attacked((side)!_side, x - 1, y) &&
             !is_piece(_side, x - 2, y) &&
             !is_piece((side)!_side, x - 2, y) &&
             !is_attacked((side)!_side, x - 2, y) &&
             !is_piece(_side, x - 3, y) &&
             !is_piece((side)!_side, x - 3, y)) {
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(x - 2, y));
            }
        }


    } else if (_piece == queen) {
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                if (dx || dy)
                    for (int d = 1; d < kBoardSize; ++d) {
                        int new_x = x + d * dx;
                        int new_y = y + d * dy;
                        if (!is_on_board(new_x, new_y)) break;
                        if (!is_piece(_side, new_x, new_y))
                            moves.push_back(give_coordinates(x, y) +
                                give_coordinates(new_x, new_y));
                        else
                            break;
                        if (is_piece((side)!_side, new_x, new_y))
                            break;
                    }
    } else if (_piece == rook) {
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                if ((dx || dy) && (!dx || !dy))
                    for (int d = 1; d < kBoardSize; ++d) {
                        int new_x = x + d * dx;
                        int new_y = y + d * dy;
                        if (!is_on_board(new_x, new_y)) break;
                        if (!is_piece(_side, new_x, new_y))
                            moves.push_back(give_coordinates(x, y) +
                                give_coordinates(new_x, new_y));
                        else
                            break;
                        if (is_piece((side)!_side, new_x, new_y))
                            break;
                    }
    } else if (_piece == knight) {
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                if (dx && dy) {
                    int new_x = x + 2* dx;
                    int new_y = y + dy;
                    if (is_on_board(new_x, new_y))
                        if (!is_piece(_side, new_x, new_y))
                            moves.push_back(give_coordinates(x, y) +
                                give_coordinates(new_x, new_y));

                    new_x = x + dx;
                    new_y = y + 2 * dy;
                    if (is_on_board(new_x, new_y))
                        if (!is_piece(_side, new_x, new_y))
                            moves.push_back(give_coordinates(x, y) +
                                give_coordinates(new_x, new_y));
                }
    } else if (_piece == bishop) {
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                if (dx && dy)
                    for (int d = 1; d < kBoardSize; ++d) {
                        int new_x = x + d * dx;
                        int new_y = y + d * dy;
                        if (!is_on_board(new_x, new_y)) break;
                        if (!is_piece(_side, new_x, new_y))
                            moves.push_back(give_coordinates(x, y) +
                                give_coordinates(new_x, new_y));
                        else
                            break;
                        if (is_piece((side)!_side, new_x, new_y))
                            break;
                    }
    } else if (_piece == pawn) {
        int dy[kSides] = {1, -1};
        int start_line[kSides] = {1, kBoardSize - 2};
        int final_line[kSides] = {kBoardSize - 1, 0};


        // Forward moves

        int new_x = x;
        int new_y = y + dy[_side];
        if (is_on_board(new_x, new_y) &&
         !is_piece(_side, new_x, new_y) &&
         !is_piece((side)!_side, new_x, new_y)) {
            if (new_y != final_line[_side])
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(new_x, new_y));
            else {
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(new_x, new_y) + "q");
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(new_x, new_y) + "r");
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(new_x, new_y) + "n");
                moves.push_back(give_coordinates(x, y) +
                 give_coordinates(new_x, new_y) + "b");
            }

            if (y == start_line[_side]) {
                new_x = x;
                new_y = y + 2 * dy[_side];
                if (!is_piece(_side, new_x, new_y) &&
                 !is_piece((side)!_side, new_x, new_y)) {
                    moves.push_back(give_coordinates(x, y) +
                     give_coordinates(new_x, new_y));
                }
            }
        }

        // Diagonal moves

        for (int dx = -1; dx <= 1; ++dx)
            if (dx) {
                new_x = x + dx;
                new_y = y + dy[_side];
                if (is_on_board(new_x, new_y) &&
                 (is_piece((side)!_side, new_x, new_y) ||
                 give_coordinates(new_x, new_y) == en_passant_)) {
                    if (new_y != final_line[_side])
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y));
                    else {
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y) + "q");
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y) + "r");
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y) + "n");
                        moves.push_back(give_coordinates(x, y) +
                         give_coordinates(new_x, new_y) + "b");
                    }
                }
            }
    }

    return moves;
}

std::vector<std::string> Position::get_all_moves(side _side) {
    std::vector<std::string> moves;

    for (int _piece = 0; _piece < kPieceTypes; ++_piece)
        for (int x = 0; x < kBoardSize; ++x)
            for (int y = 0; y < kBoardSize; ++y)
                if (board_[_side][_piece][x][y]) {
                    std::vector<std::string> piece_moves = get_piece_moves(_side, (piece)_piece, x, y);
                    moves.insert(moves.end(), piece_moves.begin(), piece_moves.end());
                }

    return moves;
}

std::vector<std::string> Position::get_valid_moves(side _side) {
    std::vector<std::string> moves, valid_moves;
    Position after_move;

    moves = get_all_moves(_side);

    for (int index = 0; index < (int)moves.size(); ++index) {
        after_move = *this;
        after_move.move(moves[index]);
        if (!after_move.is_checked(_side))
            valid_moves.push_back(moves[index]);
    }

    return valid_moves;
}

void Position::move(std::string move) {
    if (!is_valid_move(move))
        throw 0;

    int x1, y1, x2, y2;
    get_coordinates(move[0], move[1], x1, y1);
    get_coordinates(move[2], move[3], x2, y2);

    // Castle moves and validation

    if (move == "e1g1") {
        board_[side_to_move_][rook][0][7] = false;
        board_[side_to_move_][rook][0][5] = true;
    } else if (move == "e1c1") {
        board_[side_to_move_][rook][0][0] = false;
        board_[side_to_move_][rook][0][3] = true;
    } else if (move == "e8g8") {
        board_[side_to_move_][rook][7][7] = false;
        board_[side_to_move_][rook][7][5] = true;
    } else if (move == "e8c8") {
        board_[side_to_move_][rook][7][0] = false;
        board_[side_to_move_][rook][7][3] = true;
    }

    if (move.find("e1") != -1)
        castle_[white][king] = castle_[white][queen] = false;
    if (move.find("a1") != -1)
        castle_[white][queen] = false;
    if (move.find("h1") != -1)
        castle_[white][king] = false;

    if (move.find("e8") != -1)
        castle_[black][king] = castle_[black][queen] = false;
    if (move.find("a8") != -1)
        castle_[black][queen] = false;
    if (move.find("h8") != -1)
        castle_[black][king] = false;

    // Normal moves

    int _piece;
    for (_piece = 0; _piece < kPieceTypes; ++_piece)
        if (board_[side_to_move_][_piece][x1][y1])
            break;

    board_[side_to_move_][_piece][x1][y1] = false;

    // Pawn promotion
    if ((int)move.size() == 5) {
        switch (move[4]) {
            case ('q') : _piece = queen; break;
            case ('r') : _piece = rook; break;
            case ('n') : _piece = knight; break;
            case ('b') : _piece = bishop; break;
        }
    }

    board_[side_to_move_][_piece][x2][y2] = true;
    for (_piece = 0; _piece < kPieceTypes; ++_piece)
        board_[(side)!side_to_move_][_piece][x2][y2] = false;

    if (side_to_move_ == black)
        ++move_number_;
    ++moves_since_capture_;
    side_to_move_ = (side)!side_to_move_;

    compute_attacked();
}
